package com.learn.redis.concurrence.redpacket;

import io.lettuce.core.ScriptOutputType;
import io.lettuce.core.SetArgs;
import io.lettuce.core.api.async.RedisAsyncCommands;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;
import utils.json.DoubleUtil;

import java.util.Objects;
import java.util.Random;

/**
 * @author : Yu.Tan
 * @date : 2022/10/12 17:55
 * @desc : **
 */
@Service
@Slf4j
public class RedPacketService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static final Long RELEASE_SUCCESS = 1L;
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "EX";

    /**
     * 默认等待时间,单位毫秒
     */
    private final long DEFAULT_SLEEP_TIME = 500;


    public Integer grabRedOne(String redPacketId, String userId) {
        Integer money = 0;
        boolean lock=false;
        try {
            Long num = redisTemplate.opsForValue().decrement(redPacketId + "_num", 1);
            if (num >= 0){
                lock = tryLock(redPacketId, redPacketId, 10000, 5000, 300);
                if (lock){
                    Long restPeople = redisTemplate.opsForValue().decrement(redPacketId + "_restPeople", 1);

                    if (restPeople==0){
                        money = Integer.parseInt(redisTemplate.opsForValue().get(redPacketId + "_money").toString());
                    }else {
                        Integer restMoney = Integer.parseInt(redisTemplate.opsForValue().get(redPacketId + "_money").toString());
                        Random random=new Random();
                        money = random.nextInt((int) (restMoney / (restPeople + 1) * 2 - 1)) + 1;
                    }
                    redisTemplate.opsForValue().decrement(redPacketId + "_money", money);
                    /**
                     * 异步入库
                     */

                    /**
                     * 异步入账
                     */
                    Double amount = DoubleUtil.divide(Double.parseDouble(String.valueOf(money)), (double) 100);
                    log.info("用户{}抢红包成功，金额：{}", userId, amount);
                }else {

                    /**
                     * 获取锁失败，相当于抢红包失败，红包个数加一
                     */

                    redisTemplate.opsForValue().increment(redPacketId + "_num", 1);
                    redisTemplate.opsForValue().increment(redPacketId + "_restPeople", 1);
                    log.info("用户{}抢红包失败", userId);
                }
            }else {
                log.info("用户{}抢红包失败", userId);
            }

        } catch (NumberFormatException e) {
            redisTemplate.opsForValue().increment(redPacketId + "_num", 1);
            throw new RuntimeException(e);
        } finally {
            if (lock) {
                releaseLock(redPacketId, redPacketId);
            }
        }
        return money;
    }

    public Integer grabRedTwo(String redPacketId, String userId) {
        Integer money = 0;
        boolean lock=false;
        Integer restMoney = Integer.parseInt(redisTemplate.opsForValue().get(redPacketId + "_money").toString());
        if (restMoney>0){
            log.info("用户{}抢红包成功，剩余金额：{}", userId, restMoney);
            try {
                lock = tryLock(redPacketId, redPacketId, 10000, 5000, 300);
                if (lock){
                    Long num = redisTemplate.opsForValue().decrement(redPacketId + "_num", 1);
                    if (num>=0){

                        if (num==0){
                            money = Integer.parseInt(redisTemplate.opsForValue().get(redPacketId + "_money").toString());
                        }else {
                            Integer restRealMoney = Integer.parseInt(redisTemplate.opsForValue().get(redPacketId + "_money").toString());
                            Random random=new Random();
                            money = random.nextInt((int) (restRealMoney / (num + 1) * 2 - 1)) + 1;
                        }



                        redisTemplate.opsForValue().decrement(redPacketId + "_money", money);
                    }else {
                        log.info("用户{}抢红包失败", userId);
                    }
                }else {
                    /**
                     * 获取锁失败
                     */
                    log.info("用户{}抢红包失败", userId);
                }
            } catch (Exception e) {
                redisTemplate.opsForValue().increment(redPacketId + "_num", 1);
                e.printStackTrace();
            }finally {
                if (lock) {
                    releaseLock(redPacketId, redPacketId);
                }
            }

        }else{
            log.info("用户{}抢红包失败", userId);
        }
        return money;
    }


    public boolean tryLock(String lockKey, String requestId, long seconds, long wait, long sleep) {
        if (Objects.isNull(sleep)) {
            sleep = DEFAULT_SLEEP_TIME;
        }
        final long start = System.currentTimeMillis();
        if (wait > seconds) {
            wait = seconds;
        }
        final long end = start + wait * 1000;
        // 默认返回失败
        boolean res = false;
        // 调用了上面的lock方法
        while (!(res = tryLock(lockKey, requestId, seconds))) {
            if (System.currentTimeMillis() > end) {
                break;
            }
            try {
                Thread.sleep(sleep);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return res;
    }

    public Boolean tryLock(String lockKey, String requestId, long seconds) {
        return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
            RedisAsyncCommands commands = (RedisAsyncCommands) connection.getNativeConnection();
            @SuppressWarnings("unchecked")
            RedisSerializer<String> keySerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
            //lettuce连接包下序列化键值，否知无法用默认的ByteArrayCodec解析
            byte[] keyBytes = keySerializer.serialize(lockKey);
            byte[] valueBytes = keySerializer.serialize(requestId);
            String result = commands.getStatefulConnection().sync().set(keyBytes, valueBytes, SetArgs.Builder.nx().ex(seconds));
            if (LOCK_SUCCESS.equals(result)) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;
        });
    }


    public Boolean releaseLock(String lockKey, String requestId) {
        return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
            RedisAsyncCommands<byte[], byte[]> commands = (RedisAsyncCommands)connection.getNativeConnection();
            RedisSerializer<String> stringRedisSerializer = (RedisSerializer<String>) redisTemplate.getKeySerializer();
            byte[] keyByte = stringRedisSerializer.serialize(lockKey);
            byte[] valueByte = stringRedisSerializer.serialize(requestId);
            byte[][] keys = new byte[][]{keyByte};
            Object result = commands.getStatefulConnection().sync().eval("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end", ScriptOutputType.INTEGER, keys, new byte[][]{valueByte});
            log.info("releaseLock. result:{} | key:{} | requestId:{}", new Object[]{result, lockKey, requestId});
            return RELEASE_SUCCESS.equals(result) ? Boolean.TRUE : Boolean.FALSE;
        });
    }

}
